WooCommerce Emails are an important part of any WooCommerce store. But, sending emails in the same request can take a toll on performance. Delaying WooCommerce emails can make, for example, checkout faster.
WooCommerce core does have a way of delaying WooCommerce Emails.
They’ll delay emails for 10 seconds. Issue: You can’t define custom delays for every kind of email.
Some emails need to be sent right away or within the next 5 seconds. Some can wait for an hour or two.
Delaying WooCommerce Emails with their core feature
Since WooCommerce does by default, offer a way to delay emails, let’s first check how to do that.
It’s really simple. Inside of the class WC_Emails
, you can find the filter:
woocommerce_defer_transactional_emails
By adding this snippet of code inside of your own plugin, mu-plugins folder or functions.php of a theme, you’ll enable their feature.
add_filter( 'woocommerce_defer_transactional_emails', '__return_true' );
That’s it. Now, every email sent by WooCommerce will be delayed for 10 seconds.
But, is that enough for your store? Do you really want every email to be delayed for only 10 seconds?
Understanding WooCommerce Emails
Before we can code our custom delay for WooCommerce Emails, we need to understand how WC_Emails
class and all WC_Email
classes work.
WC_Emails class has a method init_transactional_emails
. Inside of that method, there are actions defined that will trigger sending or queuing emails.
public static function init_transactional_emails() {
$email_actions = apply_filters(
'woocommerce_email_actions',
array(
'woocommerce_low_stock',
'woocommerce_no_stock',
'woocommerce_product_on_backorder',
'woocommerce_order_status_pending_to_processing',
'woocommerce_order_status_pending_to_completed',
'woocommerce_order_status_processing_to_cancelled',
'woocommerce_order_status_pending_to_failed',
'woocommerce_order_status_pending_to_on-hold',
'woocommerce_order_status_failed_to_processing',
'woocommerce_order_status_failed_to_completed',
'woocommerce_order_status_failed_to_on-hold',
'woocommerce_order_status_cancelled_to_processing',
'woocommerce_order_status_cancelled_to_completed',
'woocommerce_order_status_cancelled_to_on-hold',
'woocommerce_order_status_on-hold_to_processing',
'woocommerce_order_status_on-hold_to_cancelled',
'woocommerce_order_status_on-hold_to_failed',
'woocommerce_order_status_completed',
'woocommerce_order_fully_refunded',
'woocommerce_order_partially_refunded',
'woocommerce_new_customer_note',
'woocommerce_created_customer',
)
);
if ( apply_filters( 'woocommerce_defer_transactional_emails', false ) ) {
self::$background_emailer = new WC_Background_Emailer();
foreach ( $email_actions as $action ) {
add_action( $action, array( __CLASS__, 'queue_transactional_email' ), 10, 10 );
}
} else {
foreach ( $email_actions as $action ) {
add_action( $action, array( __CLASS__, 'send_transactional_email' ), 10, 10 );
}
}
}
Then, inside of both of those methods (send or queue ones), WooCommerce triggers a different action.
They construct an action based on those which they hooked into and appending _notification
to it.
For example, if an order is marked as completed, the action woocommerce_order_status_completed
will trigger. Then, WC_Emails will trigger an action woocommerce_order_status_completed_notification
.
There is an email class WC_Email_Customer_Completed_Order
that hooks into woocommerce_order_status_completed_notification
to send the email.
Since we are creating our custom email delay system or specific emails, we need to remove such emails so they won’t be triggered by WC_Emails flow.
Custom WooCommerce Email Delay
We’ll use the above action woocommerce_order_status_completed
in here. Let’s say we want to delay emails that are sent to the customer when an order is marked as complete.
First, let’s remove the action from emails. That way, we are sure when an order is completed, not a single email hooked into that action will be sent.
We are using the function array_diff
here so the above list of hooks will be returned without the hooks defined in $actions_to_remove
.
After this is done, we need to hook our own custom function into that action. We will use this function to queue the same email we just skipped.
Here, we first check in which filter are we currently with our function.
By using the switch, we can define multiple hooks if we need to.
Then, if we are sure we are inside of a hook (filter) which is used for delaying an email, we’ll use the function as_schedule_single_action
to schedule the email.
The function as_schedule_single_action
is part of the Action Scheduler that comes with WooCommerce.
Now, when that scheduled action is run, it will trigger our custom hook send_queued_email
.
When we scheduled it, we saved the filter and arguments to be sent. By doing that, we made our job much easier when it comes to sending emails. We can re-use what WooCommerce uses when such a hook is triggered.
That’s it. With this, WooCommerce will again create the required hook for the specific email to be sent.
Class to manage it all
Let’s create now a class that will be extensible by other plugins and easier to maintain all our emails that we decide to delay.
We’ll use the same code we wrote above, wrap it with a class and define a bit more methods for maintainability.
This part is available only to the members. If you want to become a member and support my work go to this link and subscribe: Become a Member
Conclusion
By delaying emails, you can create a better peformant site as the checkout page load won’t take long after the order is paid for. Some email services might be slower than wanted, at some times, so this way, we’re making sure the user experience won’t be impacted by it.
If you’re interested in how WooCommerce emails work, how to extend existing or create new ones, and how to make them responsive, check my new course: WooCommerce Email Masterclass.
Become a Sponsor
Cool – I wrote something quite similar back in 2020.
https://www.damiencarbery.com/2020/04/defer-woocommerce-emails-for-a-few-minutes/
We wouldn’t need this if Woo provided a filter for the length of the delay.
Getting a syntax error on this line
case:’woocommerce_order_status_return-no-issues’;
Unexpected “:”
Just a couple of things I have noticed.
case: ‘woocommerce_order_status_completed’;
there is no need for the colon ‘:’
There is a speller
$delay = 2 * HOUR_IN_SECODS;
should be
$delay = 2 * HOUR_IN_SECONDS;
thanks for the tutorial, really helpful!